/* node.cc
* libxml++ and this file are copyright (C) 2000 by Ari Johnson, and
* are covered by the GNU Lesser General Public License, which should be
* included with libxml++ as the file COPYING.
*/

#include "PreCompile.h"
#include "element.h"
#include "node.h"
#include "../exceptions/internal_error.h"

#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/tree.h>

#include <iostream>
#include <assert.h>

namespace Xml
{

  Node::Node(xmlNode* node)
    : impl_(node)
  {
    impl_->_private = this;
  }

  Node::~Node()
  {}

  const Element* Node::get_parent() const
  {
    return cobj()->parent && cobj()->parent->type == ::XML_ELEMENT_NODE ?
      static_cast<const Element*>(cobj()->parent->_private) : NULL;
  }

  Element* Node::get_parent()
  {
    return cobj()->parent && cobj()->parent->type == ::XML_ELEMENT_NODE ?
      static_cast<Element*>(cobj()->parent->_private) : NULL;
  }

  const Node* Node::get_next_sibling() const
  {
    return const_cast<Node*>(this)->get_next_sibling();
  }

  Node* Node::get_next_sibling()
  {
    return cobj()->next ?
      static_cast<Node*>(cobj()->next->_private) : NULL;
  }

  const Node* Node::get_previous_sibling() const
  {
    return const_cast<Node*>(this)->get_previous_sibling();
  }

  Node* Node::get_previous_sibling()
  {
    return cobj()->prev ?
      static_cast<Node*>(cobj()->prev->_private) : NULL;
  }

  NodeList Node::get_children(const Glib::ustring& name)
  {
    xmlNode* child = impl_->children;
    if(!child)
      return NodeList();

    NodeList children;
    do
    {
      if(child->_private)
      {
        if(name.empty() || name == (const char*)child->name)
          children.push_back(reinterpret_cast<Node*>(child->_private));
      }
      else
      {
        //This should not happen:
        //This is for debugging only:
        //if(child->type == XML_ENTITY_DECL)
        //{
        //  xmlEntity* centity = (xmlEntity*)child;
        //  std::cerr << "Node::get_children(): unexpected unwrapped Entity Declaration node name =" << centity->name << std::endl;
        //}
      }
    }
    while((child = child->next));

    return children;
  }

  const NodeList Node::get_children(const Glib::ustring& name) const
  {
    return const_cast<Node*>(this)->get_children(name);
  }

  Element* Node::add_child(const Glib::ustring& name,
    const Glib::ustring& ns_prefix)
  {
    _xmlNode* child = create_new_child_node(name, ns_prefix);
    if(!child)
      return 0;

    _xmlNode* node = xmlAddChild(impl_, child);
    if(node)
      return static_cast<Element*>(node->_private);
    else
      return 0;
  }

  Element* Node::add_child(Node* previous_sibling,
    const Glib::ustring& name,
    const Glib::ustring& ns_prefix)
  {
    if(!previous_sibling)
      return 0;

    _xmlNode* child = create_new_child_node(name, ns_prefix);
    if(!child)
      return 0;

    _xmlNode* node = xmlAddNextSibling(previous_sibling->cobj(), child);
    if(node)
      return static_cast<Element*>(node->_private);
    else
      return 0;
  }

  Element* Node::add_child_before(Node* next_sibling,
    const Glib::ustring& name,
    const Glib::ustring& ns_prefix)
  {
    if(!next_sibling)
      return 0;

    _xmlNode* child = create_new_child_node(name, ns_prefix);
    if(!child)
      return 0;

    _xmlNode* node = xmlAddPrevSibling(next_sibling->cobj(), child);
    if(node)
      return static_cast<Element*>(node->_private);
    else
      return 0;
  }

  _xmlNode* Node::create_new_child_node(const Glib::ustring& name, const Glib::ustring& ns_prefix)
  {
    xmlNs* ns = 0;

    if(impl_->type != ::XML_ELEMENT_NODE)
    {
      throw InternalError("You can only add child nodes to element nodes");
    }

    //Ignore the namespace if none was specified:
    if(!ns_prefix.empty())
    {
      //Use the existing namespace if one exists:
      ns = xmlSearchNs(impl_->doc, impl_, (const xmlChar*)ns_prefix.c_str());
      if (!ns)
      {
        throw Exception("The namespace prefix (" + ns_prefix + ") has not been declared.");
      }
    }

    return xmlNewNode(ns, (const xmlChar*)name.c_str());
  }


  void Node::remove_child(Node* node)
  {
    //TODO: Allow a node to be removed without deleting it, to allow it to be moved?
    //This would require a more complex memory management API.
    xmlUnlinkNode(node->cobj());
    xmlFreeNode(node->cobj()); //The C++ instance will be deleted in a callback.
  }

  Node* Node::import_node(const Node* node, bool recursive)
  {
    //Create the node, by copying:
    xmlNode* imported_node = xmlDocCopyNode(const_cast<xmlNode*>(node->cobj()), impl_->doc, recursive);
    if (!imported_node)
    {
      throw Exception("Unable to import node");
    }

    //Add the node:
    xmlNode* added_node = xmlAddChild(this->cobj(),imported_node);
    if (!added_node)
    {
      xmlFreeNode(imported_node);

      throw Exception("Unable to add imported node to current node");
    }

    return static_cast<Node*>(imported_node->_private);
  }

  Glib::ustring Node::get_name() const
  {
    return impl_->name ? (const char*)impl_->name : "";
  }

  void Node::set_name(const Glib::ustring& name)
  {
    xmlNodeSetName( impl_, (const xmlChar *)name.c_str() );
  }

  int Node::get_line() const
  {
    return XML_GET_LINE(impl_);
  }


  xmlNode* Node::cobj()
  {
    return impl_;
  }

  const xmlNode* Node::cobj() const
  {
    return impl_;
  }

  Glib::ustring Node::get_path() const
  {
    xmlChar* path = xmlGetNodePath(impl_);
    Glib::ustring retn = path ? (char*)path : "";
    xmlFree(path);
    return retn;
  }

  static NodeSet find_impl(xmlXPathContext* ctxt, const Glib::ustring& xpath)
  {
    xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);

    if(!result)
    {
      xmlXPathFreeContext(ctxt);

      throw Exception("Invalid XPath: " + xpath);
    }

    if(result->type != XPATH_NODESET)
    {
      xmlXPathFreeObject(result);
      xmlXPathFreeContext(ctxt);

      throw InternalError("Only nodeset result types are supported.");
    }

    xmlNodeSet* nodeset = result->nodesetval;
    NodeSet nodes;
    if( nodeset )
    {
      nodes.reserve( nodeset->nodeNr );
      for (int i = 0; i != nodeset->nodeNr; ++i)
        nodes.push_back(static_cast<Node*>(nodeset->nodeTab[i]->_private));
    }
    else
    {
      // return empty set
    }

    xmlXPathFreeObject(result);
    xmlXPathFreeContext(ctxt);

    return nodes;
  }

  NodeSet Node::find(const Glib::ustring& xpath) const
  {
    xmlXPathContext* ctxt = xmlXPathNewContext(impl_->doc);
    ctxt->node = impl_;

    return find_impl(ctxt, xpath);
  }

  NodeSet Node::find(const Glib::ustring& xpath,
    const PrefixNsMap& namespaces) const
  {
    xmlXPathContext* ctxt = xmlXPathNewContext(impl_->doc);
    ctxt->node = impl_;

    for (PrefixNsMap::const_iterator it=namespaces.begin();
      it != namespaces.end(); it++)
      xmlXPathRegisterNs(ctxt,
      reinterpret_cast<const xmlChar*>(it->first.c_str()),
      reinterpret_cast<const xmlChar*>(it->second.c_str()));

    return find_impl(ctxt, xpath);
  }

  Glib::ustring Node::get_namespace_prefix() const
  {
    if(impl_->type == ::XML_DOCUMENT_NODE)
    {
      //impl_ is actually of type xmlDoc, instead of just xmlNode.
      //libxml does not always use GObject-style inheritance, so xmlDoc does not have all the same struct fields as xmlNode.
      //Therefore, a call to impl_->ns would be invalid.
      //This can be an issue when calling this method on a Node returned by Node::find().
      //See the TODO comment on Document, suggesting that Document should derived from Node.

      return Glib::ustring();
    }

    if(impl_ && impl_->ns && impl_->ns->prefix)
      return (char*)impl_->ns->prefix;
    else
      return Glib::ustring();
  }

  Glib::ustring Node::get_namespace_uri() const
  {
    if(impl_->type == ::XML_DOCUMENT_NODE)
    {
      //impl_ is actually of type xmlDoc, instead of just xmlNode.
      //libxml does not always use GObject-style inheritance, so xmlDoc does not have all the same struct fields as xmlNode.
      //Therefore, a call to impl_->ns would be invalid.
      //This can be an issue when calling this method on a Node returned by Node::find().
      //See the TODO comment on Document, suggesting that Document should derived from Node.

      return Glib::ustring();
    }

    if(impl_ && impl_->ns && impl_->ns->href)
      return (char*)impl_->ns->href;
    else
      return Glib::ustring();
  }

  void Node::set_namespace(const Glib::ustring& ns_prefix)
  {
    //Look for the existing namespace to use:
    xmlNs* ns = xmlSearchNs( cobj()->doc, cobj(), (xmlChar*)(ns_prefix.empty() ? 0 : ns_prefix.c_str()) );
    if(ns)
    {
      //Use it for this element:
      xmlSetNs(cobj(), ns);
    }
    else
    {
      throw Exception("The namespace (" + ns_prefix + ") has not been declared.");
    }
  }

  Document* Node::get_document()
  {
    assert(impl_->doc);
    Document *ret = (Document*)impl_->doc->_private;
    assert(ret);
    return ret;
  }

  const Document* Node::get_document() const
  {
    assert(impl_->doc);
    Document *ret = (Document*)impl_->doc->_private;
    assert(ret);
    return ret;
  }

  NodeType Node::get_node_type() const
  {
    return (NodeType)impl_->type;
  }
}
